package com.lianxi.es;


import cn.hutool.http.HtmlUtil;
import cn.hutool.json.JSONUtil;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.lianxi.pojo.Demo;
import com.lianxi.pojo.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.elasticsearch.action.search.SearchRequest;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.client.RequestOptions;
import org.elasticsearch.client.RestHighLevelClient;
import org.elasticsearch.index.query.BoolQueryBuilder;
import org.elasticsearch.index.query.QueryBuilders;
import org.elasticsearch.script.ScriptType;
import org.elasticsearch.script.mustache.SearchTemplateRequest;
import org.elasticsearch.script.mustache.SearchTemplateResponse;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.builder.SearchSourceBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Component;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;

import static org.elasticsearch.index.query.QueryBuilders.rangeQuery;
import static org.elasticsearch.index.query.QueryBuilders.termQuery;

/**
 * @author：张高飞
 * @since： 2022/3/17 16:37
 */
@Slf4j
@Component
public class EsHighServiceSearch extends CommonEsHighService {

    @Autowired
    private RestHighLevelClient client;

    /*****************************构建查询参数****************************************/
//构建查询参数
    public BoolQueryBuilder makeQueryParams(User user) {
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        //精确查找
        if (user.getAge() != null) {
            boolQueryBuilder.must(termQuery("age", String.valueOf(user.getAge())));
        }

        //模糊匹配
        if (!StringUtils.isEmpty(user.getName())) {
            boolQueryBuilder.must(QueryBuilders.wildcardQuery("name", String.format("*%s*", user.getName())));
        }
        return boolQueryBuilder;
    }

    /**************************************分页条件查询***************************************/
    public List<User> getListFromEs(String indexName, User user,Page page) {
        Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create();
        SearchRequest request = new SearchRequest(indexName);
        SearchSourceBuilder sourceBuilder = new SearchSourceBuilder();
        sourceBuilder.query(makeQueryParams(user));//查询参数
        sourceBuilder.from((page.getPageNumber()-1)*page.getPageSize());
        sourceBuilder.size(page.getPageSize());

//        sourceBuilder.from(demo.getOffset()).size(demo.getPageSize());//分页
        sourceBuilder.sort("age", SortOrder.DESC);//排序字段
        request.source(sourceBuilder);
        SearchHit[] hits = new SearchHit[0];
        try {
            hits = client.search(request, RequestOptions.DEFAULT).getHits().getHits();
        } catch (Exception e) {
            log.error("ES查询出错: {}", e.getMessage(), e);
        }

        List<User> data = new ArrayList<>();
        for (SearchHit hit : hits) {
            data.add(gson.fromJson(hit.getSourceAsString(), User.class));
        }
        for (User datum : data) {
            System.out.println(datum);

        }

        return data;
    }


    /**
     * 加载指定es文件
     */
    private String getSearchScript(String filename){
        ClassPathResource resource = new ClassPathResource("es/"+filename);
        StringBuilder sb = new StringBuilder();
        try {
            InputStream inputStream = resource.getInputStream();
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
            String s;
            while ((s = reader.readLine()) != null) {
                sb.append(s);
            }
            reader.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return sb.toString();
    }

    public Page resp(Page page, SearchResponse response){
        List<Map<String,Object>> data = new ArrayList<>();
        //获取命中的文档，处理结果集
        SearchHit[] hits = response.getHits().getHits();
        for(SearchHit s : hits){
            Map<String, Object> source = s.getSourceAsMap();
            source.put("_id",s.getId());
            //高亮处理
            Set<Map.Entry<String, HighlightField>> entries = s.getHighlightFields().entrySet();
            StringBuffer sb = new StringBuffer();
            for(Map.Entry<String, HighlightField> e : entries){
                String key = e.getKey();
                String value = Arrays.toString(e.getValue().fragments());
                //分词不连贯处理
                String highLightParts = value.substring(1,value.length()-1).trim().replaceAll("</span><span style='color:red;'>","");
                if (key.equals("name")) {
                    source.put(key, highLightParts);
                } else {
                    int addBeforeLen = HtmlUtil.escape(sb.toString()).length();
                    int addAfterLen = HtmlUtil.escape(sb.toString().concat(highLightParts)).length();
                    if (addBeforeLen <= 100 && addAfterLen <= 150) {
                        sb.append(highLightParts);
                    }
                }
            }
            String tmp = sb.toString();
            source.put("description",tmp.length()>0?tmp.concat("..."):tmp);
            data.add(source);
        }
        //处理页码
//        SearchResponse resp = response.getResponse();
        Long all = response.getHits().getTotalHits().value;
        Long pageTotal = all%page.getPageSize()==0?all/page.getPageSize():all/page.getPageSize()+1;
        page.setDatas(data);
        page.setTotalRow(all.intValue());
        page.setTotalPage(pageTotal.intValue());
        return page;
    }

    /**
     * 单个或者多个等值查询的整合
     */
    private List<Map<String,Object>> addTerm(String filters){
        List<Map<String,Object>> list = new ArrayList<>();
        if (StringUtils.isBlank(filters)) {
            return list;
        }
        String[] fs = filters.split(",");
        if (fs != null && fs.length > 0) {
            for(String component : fs){
                Map<String,Object> temp = new HashMap<>();
                Map<String,Object> tempTerms = new HashMap<>();
                String[] kv = component.split(":");
                if (kv.length == 2) {
                    if(kv[1].contains("#")){
                        temp.put(kv[0], JSONUtil.parseArray(kv[1].split("#")));
                        tempTerms.put("terms",temp);
                    }else{
                        temp.put(kv[0],kv[1]);
                        tempTerms.put("term",temp);
                    }
                    list.add(tempTerms);
                }
            }
        }
        return list;
    }

    private void addSearchScoreFields(List<Map<String,Object>> searchList, String key, String value, int boost){
        Map<String,Object> functionScoreWrapper = new HashMap<>();
        Map<String,Object> functionScore = new HashMap<>();
        Map<String,Object> query = new HashMap<>();
        Map<String,Object> kv = new HashMap<>();
        kv.put(key,value);
        query.put("match_phrase",kv);
        functionScore.put("query",query);
        functionScore.put("boost",boost);
        functionScoreWrapper.put("function_score",functionScore);
        searchList.add(functionScoreWrapper);
    }


    /**
     * 详情查询
     * @param params
     * @param index
     * @return
     */
    public List<Map<String, Object>> detailSearch(Map<String, Object> params, String index[]) {
        //数据收集
        List<Map<String,Object>> data = new ArrayList<>();
        //索引类型
        SearchTemplateRequest request = new SearchTemplateRequest();
        SearchRequest searchRequest = new SearchRequest(index);
        request.setRequest(searchRequest);
        request.setScriptType(ScriptType.INLINE);
        //读取json文件
        request.setScript(getSearchScript("detail_search"));
        //设置动态参数
        Map<String, Object> scriptParams = new HashMap<>();
        //过滤条件,时间、字段
        List<Map<String,Object>> mustList = new ArrayList<>();
        //过滤已删除
        filterFalseDeleted(mustList);
        //eq
        for(Map.Entry<String, Object> el : params.entrySet()){
            Map<String,Object> temp = new HashMap<>();
            temp.put(el.getKey(),el.getValue());
            Map<String,Object> tempTerms = new HashMap<>();
            tempTerms.put("term",temp);
            mustList.add(tempTerms);
        }
        scriptParams.put("must", mustList);
        //加载scripts
        request.setScriptParams(scriptParams);
        SearchTemplateResponse response = null;
        //发送json请求
        try {
            response = client.searchTemplate(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //获取命中的文档，处理结果集
        SearchHit[] hits = response.getResponse().getHits().getHits();
        for(SearchHit s : hits){
            String _id = s.getId();
            Map<String, Object> sourceAsMap = s.getSourceAsMap();
            sourceAsMap.put("_id",_id);
            data.add(sourceAsMap);
        }
        return data;
    }

    /**
     * 通用查询
     * @param page
     * @param index
     * @param e1e2KvArr
     * @param highlightFields
     * @return
     */
    //时间范围：filter::range::relevant_time::gte2014-03-19 00:00:00lte2014-03-19 00:00:00fmtyyyy-MM-dd HH:mm:ss
    //集合：must::terms::leader::张三,李四
    //等于：must::term::leader::张三
    //不等于：must_not::term::leader::张三
    //或者：should::term::leader::张三
    //有值判断：must::exists::field::title_pic
    public Page<Map<String,Object>> commonSearch(Page page, String[] index, String[] e1e2KvArr, String[] highlightFields) {
        if (page == null) {
            page = new Page();
        }
        if (page.getPageNumber()<=0 || page.getPageSize()<=0) {
            page.setPageNumber(1);
            page.setPageSize(20);
        }
        //数据收集
        List<Map<String,Object>> data = new ArrayList<>();
        //索引类型
        SearchTemplateRequest request = new SearchTemplateRequest();
        request.setRequest(new SearchRequest(index));
        request.setScriptType(ScriptType.INLINE);
        request.setScript(getSearchScript("common_search"));
        //业务参数
        Map<String, Object> scriptParams = new HashMap<>();
        inputParams(scriptParams,e1e2KvArr);
        //高亮参数
        Map<String, Object> highlightFieldsWrapper = new HashMap<>();
        if (highlightFields != null && highlightFields.length > 0) {
            for (String highlightField : highlightFields) {
                highlightFieldsWrapper.put(highlightField,new Object());
            }
        }
        scriptParams.put("highlightFields",highlightFieldsWrapper);
        //分页参数
        scriptParams.put("from", (page.getPageNumber()-1)*page.getPageSize());
        scriptParams.put("size", page.getPageSize());
        //加载scripts
        request.setScriptParams(scriptParams);
        SearchTemplateResponse response = null;
        //发送json请求
        try {
            response = client.searchTemplate(request, RequestOptions.DEFAULT);
        } catch (IOException e) {
            e.printStackTrace();
        }
        //获取命中的文档，处理结果集
        SearchHit[] hits = response.getResponse().getHits().getHits();
        for(SearchHit s : hits){
            Map<String, Object> source = s.getSourceAsMap();
            source.put("_id",s.getId());
            //高亮处理
            Set<Map.Entry<String, HighlightField>> entries = s.getHighlightFields().entrySet();
            for(Map.Entry<String, HighlightField> e : entries){
                String key = e.getKey();
                String value = Arrays.toString(e.getValue().fragments());
                source.put(key,value);
            }
            data.add(source);
        }
        //处理页码
        SearchResponse resp = response.getResponse();
        Long all = resp.getHits().getTotalHits().value;
        Long pageTotal = all%page.getPageSize()==0?all/page.getPageSize():all/page.getPageSize()+1;
        page.setDatas(data);
        page.setTotalRow(all.intValue());
        page.setTotalPage(pageTotal.intValue());
        return page;
    }

    private void inputParams(Map<String, Object> scriptParams, String[] e1e2KvArr){
        String mustNot = "must_not";
        String must = "must";
        String should = "should";
        String filter = "filter";
        List<Map<String,Object>> mustNotList = new ArrayList<>();
        List<Map<String,Object>> mustList = new ArrayList<>();
        List<Map<String,Object>> shouldList = new ArrayList<>();
        List<Map<String,Object>> filterList = new ArrayList<>();
        filterFalseDeleted(filterList);
        if (e1e2KvArr == null || e1e2KvArr.length <= 0) {
            finalCheck(scriptParams,mustNotList,mustList,shouldList,filterList,mustNot,must,should,filter);
            return;
        }
        for (String e1e2Kv : e1e2KvArr) {
            if (e1e2Kv.startsWith(mustNot+"::")) {
                inputParam(mustNotList,e1e2Kv);
            }
            if (e1e2Kv.startsWith(must+"::")) {
                inputParam(mustList,e1e2Kv);
            }
            if (e1e2Kv.startsWith(should+"::")) {
                inputParam(shouldList,e1e2Kv);
            }
            if (e1e2Kv.startsWith(filter+"::")) {
                inputParam(filterList,e1e2Kv);
            }
        }
        finalCheck(scriptParams,mustNotList,mustList,shouldList,filterList,mustNot,must,should,filter);
    }

    private void filterFalseDeleted(List<Map<String, Object>> filterList) {
        Map<String,Object> isDeletedWrapper = new HashMap<>();
        Map<String,Object> isDeleted = new HashMap<>();
        isDeleted.put("is_deleted",false);
        isDeletedWrapper.put("term",isDeleted);
        filterList.add(isDeletedWrapper);
    }

    private void finalCheck(Map<String, Object> scriptParams, List<Map<String,Object>> mustNotList,List<Map<String,Object>> mustList,
                            List<Map<String,Object>> shouldList,List<Map<String,Object>> filterList,String mustNot,String must,String should,String filter){
        checkAndSetParamList(scriptParams,mustNotList,mustNot);
        checkAndSetParamList(scriptParams,mustList,must);
        checkAndSetParamList(scriptParams,shouldList,should);
        checkAndSetParamList(scriptParams,filterList,filter);
    }

    private void inputParam(List<Map<String,Object>> e1List, String e1e2Kv){
        String[] arr = e1e2Kv.split("::");
        String e2 = arr[1];
        String k = arr[2];
        String v = arr[3];
        Map<String, Object> kvWrapper = new HashMap<>();
        Map<String, Object> kv = new HashMap<>();
        kv.put(k,v);
        if (v.contains("gt") || v.contains("lt")) {
            Map<String, Object> vvWrapper = new HashMap<>();
            String fmt = "";
            if (v.contains("fmt")) {
                String[] fmts = v.split("fmt");
                v = fmts[0];
                fmt = fmts[1];
                vvWrapper.put("format",fmt);
            }
            if (v.contains("lte")) {
                String[] ltes = v.split("lte");
                vvWrapper.put("lte",ltes[1]);
                if (ltes[0].contains("gte")) {
                    String[] gtes = ltes[0].split("gte");
                    vvWrapper.put("gte",gtes[1]);
                }else if (ltes[0].contains("gt")) {
                    String[] gts = ltes[0].split("gt");
                    vvWrapper.put("gt",gts[1]);
                }
            }else if (v.contains("lt")) {
                String[] lts = v.split("lt");
                vvWrapper.put("lt",lts[1]);
                if (lts[0].contains("gte")) {
                    String[] gtes = lts[0].split("gte");
                    vvWrapper.put("gte",gtes[1]);
                }else if (lts[0].contains("gt")) {
                    String[] gts = lts[0].split("gt");
                    vvWrapper.put("gt",gts[1]);
                }
            }
            kv.put(k,vvWrapper);
        }

        if (v.contains(",")) {
            String[] arrStr = v.split(",");
            kv.put(k,arrStr);
        }
        kvWrapper.put(e2,kv);
        e1List.add(kvWrapper);
    }
    private void checkAndSetParamList(Map<String, Object> scriptParams,List<Map<String,Object>> e1List, String e1){
        if(e1List.size() > 0){
            scriptParams.put(e1, e1List.toArray());
        }else{
            scriptParams.put(e1, "[]");
        }
    }

}
